﻿using UnityEngine;
using System.Collections;

[RequireComponent (typeof(Rigidbody))]
[RequireComponent (typeof(LineRenderer))]
public class Robot : MonoBehaviour {
	
	string robotTeam;
	public string robotClass;
	public GameObject fireHalo;
	public Settings settings;
	
	BaseRobot robotController;
	BaseRobot.RobotStatus robotStatus;
	System.Collections.Generic.Queue<BaseRobot.RequestParams> requests;
	
	float initialDrag;
	float radarDistance = 0f;
	Vector3 radarDirection;
	float radarAngle = 0f;
	float currentAngle = 0f;
	float radarMoved = 0f;
	LineRenderer lineRenderer;
	
	// Use this for initialization
	void Start () {
		settings = GameObject.FindGameObjectWithTag("Settings").GetComponent<Settings>();
		lineRenderer = GetComponent<LineRenderer>();
		initialDrag = rigidbody.drag;
		robotTeam = "";
	
		System.Type type = System.Type.GetType(robotClass);
		robotController = (BaseRobot)System.Activator.CreateInstance(type);
		robotStatus = new BaseRobot.RobotStatus();
		requests = new System.Collections.Generic.Queue<BaseRobot.RequestParams>();
		
		robotStatus.life = settings.initialLife;
		robotController.InternalStart(this, Random.Range(0, 1000));
		
		if (robotTeam == "") robotTeam = robotClass;
	}
	
	public void SetTeam(string team) {
		if (robotTeam == "") robotTeam = team;
	}
	
	// Update is called once per frame
	void Update () {
		UpdateRobotStatus();
		robotController.InternalUpdate(Time.deltaTime, robotStatus);
		UpdateRadar();
		
		while (ProcessNextRequest());
	}
	
	void UpdateRobotStatus() {
		rigidbody.drag = initialDrag;
		
		robotStatus.fireCooldown -= Time.deltaTime;
		if (robotStatus.fireCooldown < 0f) robotStatus.fireCooldown = 0f;
		
		robotStatus.speed = rigidbody.velocity;
		robotStatus.position = transform.position;
	}
	
	void UpdateRadar() {
		if (radarAngle <= 0f || radarDirection == Vector3.zero || radarDistance <= 0f) {
			lineRenderer.enabled = false;
			return;
		}
	
		float radarMovement = Mathf.Pow(settings.radarAngleDistance/radarDistance/settings.radarAngleDistanceBase, settings.radarAngleDistancePow)*settings.radarAngleDistanceBase * Time.deltaTime;
		Vector3 lastDirection = Vector3.forward*Mathf.Cos(currentAngle*Mathf.Deg2Rad)+Vector3.left*Mathf.Sin(currentAngle*Mathf.Deg2Rad);
		currentAngle = (currentAngle+radarMovement) % 360;
		Vector3 currentDirection = Vector3.forward*Mathf.Cos(currentAngle*Mathf.Deg2Rad)+Vector3.left*Mathf.Sin(currentAngle*Mathf.Deg2Rad);
		
		lineRenderer.enabled = true;
		lineRenderer.SetPosition(1, currentDirection*radarDistance);
//		Debug.DrawLine(transform.position, transform.position+currentDirection*radarDistance);
		
		float moveDistance = Vector3.Distance(lastDirection, currentDirection);
		foreach (GameObject player in GameObject.FindGameObjectsWithTag("Player")) {
			Vector3 playerDirection = player.transform.position-transform.position;
			float playerDistance = playerDirection.magnitude;
			if (playerDistance == 0f || playerDistance > radarDistance) continue;
			playerDirection.Normalize();
			
			float playerToLastDistance = Vector3.Distance(playerDirection, lastDirection);
			float playerToCurrentDistance = Vector3.Distance(playerDirection, currentDirection);
			
			if (moveDistance > playerToLastDistance && moveDistance > playerToCurrentDistance)
				ScanHit(player);
		}
		
		radarMoved += radarMovement;
		if (radarMoved > radarAngle) {
			radarMoved = 0f;
			currentAngle = GetStartRadarAngle();
		}
	}
	
	void ScanHit(GameObject player) {
		BaseRobot.ScanResult result = new BaseRobot.ScanResult();
		Robot playerRobot = player.GetComponent<Robot>();
		
		Vector3 relativePosition = player.transform.position - transform.position;
		result.position = player.transform.position;
		result.enemyId = playerRobot.robotController.GetRobotId();
		result.direction = relativePosition.normalized;
		result.distance = relativePosition.magnitude;
		result.team = playerRobot.robotTeam;
		result.sameTeam = playerRobot.robotTeam == robotTeam;
		
		robotController.OnScanHit(result);
	}
	
	bool ProcessNextRequest() {
		if (requests.Count == 0) return false;
		
		BaseRobot.RequestParams req = requests.Dequeue();
		switch(req.request) {
		case "Push":
			Push((BaseRobot.PushParams)req);
			break;
		case "Brake":
			Brake();
			break;
		case "Scan":
			Scan((BaseRobot.ScanParams)req);
			break;
		case "Raycast":
			Raycast((BaseRobot.RaycastParams)req);
			break;
		case "Fire":
			Fire((BaseRobot.FireParams)req);
			break;
		case "Message":
			SendMessage((BaseRobot.SendMessageParams)req);
			break;
		default:
			Debug.LogWarning("Request "+req.request+" not handled");
			break;
		}
		
		return true;
	}
	
	void Request(BaseRobot.RequestParams param) {
		string request = param.request;
		foreach (BaseRobot.RequestParams req in requests)
			if (req.request == request)
				return;
		
		requests.Enqueue(param);
	}
	
	void ReceiveMessage(MessagePacket msgPacket) {
		if (msgPacket.setName != robotTeam || msgPacket.sender == robotController.GetRobotId()) return;
		robotController.OnReceiveMessage(msgPacket.message, msgPacket.sender);
	}
	
	void ReceiveDamage(int amount, int sourceId) {
		robotStatus.life -= amount;
		
		if (robotStatus.life <= 0)
			Destroy(gameObject);
		else
			robotController.OnGetDamage(amount, sourceId);
	}
	
	void Push(BaseRobot.PushParams param) {
		if (param.force.magnitude > settings.maxPush)
			param.force /= param.force.magnitude / settings.maxPush;
		
		rigidbody.AddForce(Vector3.Scale(param.force*Time.deltaTime, new Vector3(1,0,1)));
	}
	
	void Brake() {
		rigidbody.drag = settings.brakeDrag;
	}
	
	void Raycast(BaseRobot.RaycastParams param) {
		Ray ray = new Ray(transform.position, param.direction);
		RaycastHit hitInfo;
		if (Physics.Raycast(ray, out hitInfo, Mathf.Infinity, 1<<LayerMask.NameToLayer("Obstacles"))) {
			BaseRobot.RaycastResult results = new BaseRobot.RaycastResult();
			results.gameObject = hitInfo.collider.gameObject;
			results.hitPosition = hitInfo.point;
			
			robotController.OnRayHit(results);
		}
	}
	
	void SendMessage(BaseRobot.SendMessageParams param) {
		MessagePacket packet = new MessagePacket();
		packet.setName = robotTeam;
		packet.sender = robotController.GetRobotId();
		packet.message = param.message;
		
		foreach(GameObject player in GameObject.FindGameObjectsWithTag("Player"))
			player.SendMessage("ReceiveMessage", packet);
	}
	
	class MessagePacket {
		public string setName;
		public int sender;
		public string message;
	}
	
	void Fire(BaseRobot.FireParams param) {
		if (robotStatus.fireCooldown > 0) return;
		robotStatus.fireCooldown = settings.fireCooldownTime;
		
		if (param.distance > 0) {
			Ray ray = new Ray(transform.position, param.direction);
			RaycastHit hitInfo;
			if (Physics.Raycast(ray, out hitInfo, param.distance, 1<<LayerMask.NameToLayer("Obstacles")))
				param.distance = hitInfo.distance;
		}
		
		Vector3 firePosition = transform.position + param.direction.normalized*param.distance;
		GameObject fire = (GameObject)GameObject.Instantiate(fireHalo, firePosition+Vector3.up*0.5f, Quaternion.identity);
		
		foreach(GameObject player in GameObject.FindGameObjectsWithTag("Player")) {
			Vector3 playerPos = player.transform.position;
			float explosionDistance = Vector3.Distance(playerPos, firePosition);
			float damageReductionByDistance = explosionDistance*settings.damageReduction;
			int damage = (int)Mathf.Floor(Mathf.Clamp(settings.baseDamage-damageReductionByDistance,0,settings.baseDamage));
			
			if (damage > 0) {
				player.GetComponent<Robot>().ReceiveDamage(damage, robotController.GetRobotId());
				robotController.OnDealDamage(damage, player.GetComponent<Robot>().robotController.GetRobotId());
			}
		}
	}
	
	void Scan(BaseRobot.ScanParams param) {
		radarAngle = param.angle;
		radarDirection = param.direction.normalized;
		radarDistance = param.distance;
		currentAngle = GetStartRadarAngle();
		radarMoved = 0f;
	}
	
	float GetStartRadarAngle() {
		Vector2 x = new Vector2(0,1);
		Vector2 direction = new Vector2(radarDirection.x, radarDirection.z);
		float angle = Vector2.Angle(x, direction);
		Vector3 cross = Vector3.Cross(x, direction);
		if (cross.z < 0) angle = 360 - angle;
		
		return (angle-radarAngle/2f) % 360;
	}
}
